namespace Tnelab{
    export class ContextSerializer {
        private NULL_FLAG: string = "THISISNULL";
        public ToBytes(typeName: string, obj: any, bytes: Uint8Array, positon: { Offset: number }): void {
            typeName = typeName.trim();
            this.ToBytes_(typeName, obj, bytes, positon);
        }
        public FromBytes(csTypeName: string, jsTypeName: string, view: DataView, positon: { Offset: number }): any {
            csTypeName = csTypeName.trim();
            jsTypeName = jsTypeName.trim();
            return this.CreateByBytes(csTypeName, jsTypeName, view, positon);
        }      
        private ToBytes_(typeName: string, obj: any, bytes: Uint8Array, position: { Offset: number }): void {
            if (obj == null || obj == undefined) {
                for (let i = 0; i < this.NULL_FLAG.length; i++) {
                    bytes[position.Offset] = this.NULL_FLAG.charCodeAt(i);
                    position.Offset++;
                }
                return;
            }
            if (typeName.startsWith("System.Func<") || typeName.startsWith("System.Action")) {
                return this.ToBytes_("System.String", obj, bytes,position);
            }
            if (typeName.endsWith("]")) {
                this.ToBytesForArray(typeName,obj, bytes,position);
            }
            else if (typeName.startsWith("System.Collections.Generic.List")) {
                this.ToBytesForList(typeName, obj, bytes,position);
            }
            else if (typeName == "System.String") {
                this.ToBytesForString(obj, bytes,position);
            }
            else {
                if (!this.ToBytesForNumber(typeName,obj, bytes,position)) {
                    this.ToBytesForEntity(obj, bytes,position);
                }
            }
        }
        private ToBytesForString(obj: string, bytes: Uint8Array, position: { Offset: number }): void {
            if (obj === "") {
                for (let i = 0; i < 4; i++) {
                    bytes[position.Offset] = 0;
                    position.Offset++;
                }
                return;
            }
            let oldOffset = position.Offset;
            position.Offset += 4;
            for (let i = 0; i < obj.length; i++) {
                let u8a = Tnelab.UnicodeOne2Utf8Bytes(obj.charCodeAt(i));
                for (let t = 0; t < u8a.length; t++) {
                    bytes[position.Offset] = u8a[t];
                    position.Offset++;
                }
            }
            let size = position.Offset - oldOffset - 4;
            position.Offset = oldOffset;
            let view = new DataView(new Int32Array([size]).buffer);
            for (let i = 0; i < 4; i++) {
                bytes[position.Offset] = view.getUint8(i);
                position.Offset++;
            }
            position.Offset += size;
        }
        private ToBytesForNumber(typeName: string, obj: any, bytes: Uint8Array, position: { Offset: number }): boolean {
            let arrayBuffer: ArrayBuffer = null;
            switch (typeName)
            {
                case "System.Byte":
                    arrayBuffer = new Uint8Array([obj]).buffer;
                    break;
                case "System.SByte":
                    arrayBuffer = new Int8Array([obj]).buffer;
                    break;
                case "System.UInt16":
                    arrayBuffer = new Uint16Array([obj]).buffer;
                    break;
                case "System.Int16":
                    arrayBuffer = new Int16Array([obj]).buffer;
                    break;
                case "System.UInt32":
                    arrayBuffer = new Uint32Array([obj]).buffer;
                    break;
                case "System.Int32":
                    arrayBuffer = new Int32Array([obj]).buffer;
                    break;
                case "System.UInt64":
                    throw "js不支持System.UInt64序列化";
                case "System.Int64":
                    throw "js不支持System.Int64序列化";
                case "System.Single":
                    arrayBuffer = new Float32Array([obj]).buffer;
                    break;
                case "System.Double":
                    arrayBuffer = new Float64Array([obj]).buffer;
                    break;
                case "System.Boolean":
                    arrayBuffer = (obj === true ? new Uint8Array([1]).buffer : new Uint8Array([0]).buffer)
                default:
                    break;
            };
            if (arrayBuffer == null)
                return false;

            let view = new DataView(arrayBuffer);
            for (let i = 0; i < arrayBuffer.byteLength; i++) {
                bytes[position.Offset] = view.getUint8(i);
                position.Offset++;
            }
            return true;
        }
        private ToBytesForEntity(obj: any, bytes: Uint8Array, position: { Offset: number }): void {
            let propInfos = obj.GetPropInfos();
            for (let i = 0; i < propInfos.length; i++) {
                this.ToBytes_(propInfos[i].TypeName, obj.GetValueByPropName(propInfos[i].Name), bytes,position);
            }
        }
        private ToBytesForArray(typeName: string, obj: any, bytes: Uint8Array, position: { Offset: number }): void {
            let sizeView = new DataView(new Int32Array([obj.length]).buffer);
            for (let i = 0; i < sizeView.byteLength; i++) {
                bytes[position.Offset] = sizeView.getUint8(i);
                position.Offset++;
            }
            if (typeName == "System.Byte[]") {
                let len = obj.length;
                for (let i = 0; i < len; i++) {
                    bytes[position.Offset] = obj[i];
                    position.Offset++;
                }
                return;
            }
            let p = typeName.indexOf("[");
            let subTypeName = typeName.substr(0, p);
            for (var i = 0; i < obj.length; i++) {
                this.ToBytes_(subTypeName, obj[i], bytes, position);
            }
        }
        private ToBytesForList(typeName: string, obj: any, bytes: Uint8Array, position: { Offset: number }): void {
            let sizeView = new DataView(new Int32Array([obj.length]).buffer);
            for (let i = 0; i < sizeView.byteLength; i++) {
                bytes[position.Offset] = sizeView.getUint8(i);
                position.Offset++;
            }
            let p = typeName.indexOf("<");
            let subTypeName = typeName.substr(p + 1, typeName.length-1);
            for (var i = 0; i < obj.length; i++) {
                this.ToBytes_(subTypeName, obj[i], bytes, position);
            }
        }
        private GetString(view: DataView, offset: number, len: number): string {
            let str: string = "";
            let position = { Offset: offset };
            while (position.Offset < offset+len) {
                str += String.fromCharCode(Utf8OneBytesToUnicodeCode(view, position));
            }
            return str;
        }
        private CreateByBytes(csTypeName: string, jsTypeName: string, view: DataView, positon: { Offset: number }): { Offset: number }{
            if (view.byteLength - positon.Offset >= this.NULL_FLAG.length) {
                for (let i = 0; i < this.NULL_FLAG.length; i++) {
                    if (view.getUint8(positon.Offset + i) != this.NULL_FLAG.charCodeAt(i)) {
                        break;
                    }
                    positon.Offset += this.NULL_FLAG.length;
                    return null;
                }
            }
            let obj;
            if (csTypeName.endsWith(']')) {
                obj = this.FromBytesForArray(csTypeName, jsTypeName, view, positon);
            }
            else if (csTypeName.startsWith("System.Collections.Generic.List")) {
                obj = this.FromBytesForList(csTypeName, jsTypeName, view, positon);
            }
            else if (csTypeName === "System.String") {
                obj = this.FromBytesForString(csTypeName, jsTypeName, view, positon);
            }
            else {
                obj = this.FromBytesForNumber(csTypeName, jsTypeName, view, positon);
                if (obj == undefined) {
                    obj = this.FromBytesForEntity(csTypeName, jsTypeName, view, positon);
                }
            }
            return obj;
        }
        private GetArrayBuffer(csTypeName: string, jsTypeName: string, view: DataView, positon: any): any {
            let len = view.getInt32(positon.Offset,true);
            positon.Offset += 4;
            let robj = eval("new " + jsTypeName + "(" + len + ")") as any;
            switch (jsTypeName) {
                case "Int16Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getInt16(positon.Offset,true);
                        positon.Offset += 2;
                    }
                    return robj;
                case "Uint16Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getUint16(positon.Offset,true);
                        positon.Offset += 2;
                    }
                    return robj;
                case "Int32Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getInt32(positon.Offset,true);
                        positon.Offset += 4;
                    }
                    return robj;
                case "Uint32Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getUint32(positon.Offset,true);
                        positon.Offset += 4;
                    }
                    return robj;
                case "System.Int64[]":
                    throw "不支持System.Int64[]";
                case "System.UInt64[]":
                    throw "不支持System.UInt64[]";
                case "Float64Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getFloat64(positon.Offset,true);
                        positon.Offset += 8;
                    }
                    return robj;
                case "Float32Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getFloat32(positon.Offset,true);
                        positon.Offset += 4;
                    }
                    return robj;
                case "Uint8Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getUint8(positon.Offset);
                        positon.Offset += 1;
                    }
                    return robj;
                case "Int8Array":
                    for (let i = 0; i < len; i++) {
                        robj[i] = view.getInt8(positon.Offset);
                        positon.Offset += 1;
                    }
                    return robj;
            }
            throw "不支持" + csTypeName;
        }
        private FromBytesForArray(csTypeName: string, jsTypeName: string, view: DataView, positon: any): any {
            if (jsTypeName.endsWith("Array")) {
                return this.GetArrayBuffer(csTypeName, jsTypeName, view, positon);
            }
            let len = view.getInt32(positon.Offset, true);
            positon.Offset += 4;
            let objs = eval("new Array(" + len + ")") as any;
            let jsSubType = jsTypeName.substring(0, jsTypeName.length - 2);
            let csSubType = csTypeName.substring(0, csTypeName.length - 2);
            for (let i = 0; i < len; i++) {
                objs[i] = this.CreateByBytes(csSubType, jsSubType, view, positon);
            }
            return objs;
        }
        private FromBytesForList(csTypeName: string, jsTypeName: string, view: DataView, positon: any): any {
            if (jsTypeName.endsWith("Array")) {
                return this.GetArrayBuffer(csTypeName, jsTypeName, view, positon);
            }
            //alert(csTypeName + ":" + jsTypeName + ":"+positon.Offset);
            let len = view.getInt32(positon.Offset,true);
            positon.Offset += 4;
            let objs = eval("new Array(" + len + ")") as any;
            let jsSubType = jsTypeName.substring("Array<".length, jsTypeName.length - 1);
            let p = csTypeName.indexOf("<");
            let csSubType = csTypeName.substring(p+1, csTypeName.length - 1);
            for (let i = 0; i < len; i++) {
                objs[i] = this.CreateByBytes(csSubType, jsSubType, view, positon);
            }
            return objs;
        }
        private FromBytesForString(csTypeName: string, jsTypeName: string, view: DataView, positon: any): any {
            let len = view.getInt32(positon.Offset, true);
            positon.Offset += 4;
            let str =this.GetString(view, positon.Offset, len);
            positon.Offset += len;
            return str;
        }
        private FromBytesForNumber(csTypeName: string, jsTypeName: string, view: DataView, positon: { Offset: number }): any {
            let robj = undefined;
            switch (csTypeName) {
                case "System.Byte":
                    robj = view.getUint8(positon.Offset);
                    positon.Offset += 1;
                    break;
                case "System.SByte":
                    robj = view.getInt8(positon.Offset);
                    positon.Offset += 1;
                    break;
                case "System.UInt16":
                    robj = view.getUint16(positon.Offset,true);
                    positon.Offset += 2;
                    break;
                case "System.Int16":
                    robj = view.getInt16(positon.Offset,true);
                    positon.Offset += 2;
                    break;
                case "System.UInt32":
                    robj = view.getUint32(positon.Offset,true);
                    positon.Offset += 4;
                    break;
                case "System.Int32":
                    robj = view.getInt32(positon.Offset,true);
                    positon.Offset += 4;
                    break;
                case "System.UInt64":
                    throw "js不支持System.UInt64序列化";
                case "System.Int64":
                    throw "js不支持System.Int64序列化";
                case "System.Single":
                    robj = view.getFloat32(positon.Offset,true);
                    positon.Offset += 4;
                    break;
                case "System.Double":
                    robj = view.getFloat64(positon.Offset,true);
                    positon.Offset += 8;
                    break;
                case "System.Boolean":
                    let r = view.getUint8(positon.Offset);
                    positon.Offset += 1;
                    robj = r === 1 ? true : false;
                    break;
            };
            return robj;
        }
        private FromBytesForEntity(csTypeName: string, jsTypeName: string, view: DataView, positon: any): any {
            let obj = eval("new "+jsTypeName+"()") as any;
            let propInfos = obj.GetPropInfos();
            for (let i = 0; i < propInfos.length;i++)
            {
                obj.SetValueByPropName(propInfos[i].Name, this.CreateByBytes(propInfos[i].TypeName, propInfos[i].JsTypeName, view, positon));
            }
            return obj;
        }
    }
}
